﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Collections;
using System.Linq.Expressions;

namespace Pharmacy.Data
{
    public class ObjectContextMock : IObjectContext
    {
        const string UPDATEDDATEFIELD = "UpdatedDate";
        const string UPDATEDUSERFIELD = "UpdatedUser";
        const string OPTIMISTICLOCKFIELDNAME = "OptimisticLockField";

        List<object> listObjectSet = new List<object>();
        List<IdentityFieldReg> listIdentityFields = new List<IdentityFieldReg>();
        List<Object> listCommand = new List<object>();
        public void RegIdentityField(params IdentityFieldReg[] paras)
        {
            foreach (IdentityFieldReg identyField in paras)
            {
                listIdentityFields.Add(identyField);
            }
        }

        public void RegExecCommand<T>(Func<IEnumerable<T>> func,string command, params object[] parameters)
        {
            listCommand.Add(new ExecCommandReg<T>() { 
                Delegate = func,
                Command = command,
                Parameters = parameters
            });
        }

        public void RegExecCommand(Action act, string command, params object[] parameters)
        {
            Func<IEnumerable<Object>> func = new Func<IEnumerable<object>>(() => {
                act();
                return null;
            });
            listCommand.Add(new ExecCommandReg<Object>()
            {
                Delegate = func,
                Command = command,
                Parameters = parameters
            });
        }

        public DateTime GetCurrentDateTime()
        {
            return DateTime.UtcNow;
        }

        public string UserName
        {
            get
            {
                return "MockUser";
            }
            set
            {
 
            }
        }

        public IObjectContext CreateNew(bool withNewConnection = true)
        {
            if (withNewConnection) return new ObjectContextMock();
            else return this;
        }

        public IEnumerable<T> ExecCommand<T>(string command, params object[] parameters)
        {
            var cmd = listCommand.OfType<ExecCommandReg<T>>().Where(m => m.Command == command).Where((m) =>
                {
                    foreach (var para in parameters)
                    {
                        if (!m.Parameters.Contains(para))
                        {
                            return false;
                        }
                    }
                    return true;
                }
                ).FirstOrDefault();
            if (cmd != null) return cmd.Delegate();
            throw new Exception("Function is not registed");
        }

        public void ExecCommand(string command, params object[] parameters)
        {
            var cmd = listCommand.OfType<ExecCommandReg<Object>>().Where(m => m.Command == command).Where((m) =>
            {
                foreach (var para in parameters)
                {
                    if (!m.Parameters.Contains(para))
                    {
                        return false;
                    }
                }
                return true;
            }
                ).FirstOrDefault();
            if (cmd != null) cmd.Delegate();
            else
                throw new Exception("Function is not registed");
        }

        public IObjectSet<T> CreateObjectSet<T>() where T : class
        {
            foreach (Object obj in listObjectSet)
            {
                if (obj is IObjectSet<T>)
                {
                    return obj as IObjectSet<T>;
                }
            }
            IObjectSet<T> ret = new ObjectSetMock<T>(this);
            listObjectSet.Add(ret);
            return ret;
        }

        public IObjectSet<T> CreateObjectSet<T>(MergeOption mergeOption) where T : class
        {
            return CreateObjectSet<T>();
        }

        public void AttachAsModify<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class {
            CreateObjectSet<T>().Attach(entity);
        }

        public void AddObject<TEntity>(TEntity entity) where TEntity:class
        {
            //Add Single
            var objSets = (ObjectSetMock<TEntity>)this.CreateObjectSet<TEntity>();
            objSets.InternalAddObject(entity);
            //Add Relation Object
            foreach (var relobj in getAllRelationObjects(entity, entity))
            {
                var dynobjset = this.GetType().GetMethods().Where(k => k.Name == "CreateObjectSet").First().MakeGenericMethod(relobj.GetType()).Invoke(this,null);
                dynobjset.GetType().GetMethod("InternalAddObject",System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(dynobjset, new object[] {relobj});
            }
        }

        private IEnumerable getAllRelationObjects<TEntity>(TEntity entity,TEntity entityori) where TEntity : class
        {

            foreach (var obj in getRelationObjects(entity))
            {
                if (obj != entityori)
                {
                    yield return obj;
                    foreach (var obj2 in getAllRelationObjects(obj, entityori))
                    {
                        if(obj2 != entityori)
                            yield return obj2;
                    }
                }
            }
        }

        private IEnumerable getRelationObjects<TEntity>(TEntity entity) where TEntity : class
        {
            var relationCollection = entity.GetType().GetProperties().Where(m => m.GetCustomAttributes(false).OfType<EdmRelationshipNavigationPropertyAttribute>().Count() > 0);
            foreach (var relate in relationCollection)
            {
                if (relate.PropertyType.IsSubclassOf(typeof(EntityObject)))
                {
                    var ret = relate.GetValue(entity, null);
                    if (ret != null)
                    {
                        yield return ret;
                    }
                }
                else
                {
                    var collections = relate.GetValue(entity, null) as IEnumerable;
                    if(collections != null)
                    {
                        foreach (var obj in collections)
                        {
                            yield return obj;
                        }
                    }
                }
            }
        }

        public void SaveChanges()
        {
            foreach (var set in listObjectSet)
            {
                set.GetType().GetMethod("Save").Invoke(set,new Object[]{listIdentityFields});
            }
        }

        public void Dispose()
        {
            foreach (IDisposable dis in listObjectSet)
            {
                dis.Dispose();
            }
        }

        /// <summary>
        /// Update an object in object context with data from data source
        /// </summary>
        /// <param name="refreshMode"></param>
        /// <param name="entity"></param>
        public void Reload(RefreshMode refreshMode, object entity)
        {
            
        }

        /// <summary>
        /// Update a collection of objects in object context with data from data source
        /// </summary>
        /// <typeparam name="T">type of entity</typeparam>
        /// <param name="refreshMode"></param>
        /// <param name="entity">object is needed to reload</param>
        public void Reload(RefreshMode refreshMode, IEnumerable collection)
        {
            
        }

        /// <summary>
        /// Mock Object Set
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        private class ObjectSetMock<TEntity> : IObjectSet<TEntity>, IDisposable where TEntity : class
        {
            HashSet<TEntity> listData = new HashSet<TEntity>();
            HashSet<TEntity> listDataAdded = new HashSet<TEntity>();
            ObjectContextMock context = null;
            private volatile static int identity = 0;

            public ObjectSetMock(ObjectContextMock context)
            {
                this.context = context;
            }

            internal void InternalAddObject(TEntity entity)
            {
                listData.Add(entity);
                listDataAdded.Add(entity);
            }

            public void AddObject(TEntity entity)
            {
                this.context.AddObject(entity);
            }

            public void Attach(TEntity entity)
            {
                listData.Add(entity);
            }

            public void DeleteObject(TEntity entity)
            {
                listData.Remove(entity);
            }

            public void Detach(TEntity entity)
            {
                listData.Remove(entity);
            }

            public IEnumerator<TEntity> GetEnumerator()
            {
                return listData.AsQueryable().GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return listData.AsQueryable().GetEnumerator();
            }

            public Type ElementType
            {
                get
                {
                    return typeof(TEntity);
                }
            }

            public System.Linq.Expressions.Expression Expression
            {
                get { return listData.AsQueryable().Expression; }
            }

            public IQueryProvider Provider
            {
                get { return listData.AsQueryable().Provider; }
            }

            public void Save(List<IdentityFieldReg> listIdentityFields)
            {
                foreach (IdentityFieldReg id in listIdentityFields.Where(m => m.Type == typeof(TEntity)))
                {
                    identity++;
                    foreach (var entity in listDataAdded)
                    {
                        entity.GetType().GetProperty(id.FieldName).SetValue(entity, identity, null);
                    }
                }
            }

            public void Dispose()
            {
                listData.Clear();
            }
        }
    }
    public class IdentityFieldReg
    {
        public Type Type { get; set; }
        public string FieldName { get; set; }
    }

    public class ExecCommandReg<T>
    {
        public Func<IEnumerable<T>> Delegate { get; set; }
        public string Command { get; set; }
        public object[] Parameters { get; set; }
    }
}
